import _ from 'lodash'
import { toPropertyList, toCommandLine } from './providers/object'

export class Configuration {
    configurations = new Map()
    opts = {
        isCreateChanges: true
    }

    constructor(options) {
        _.merge(this.opts, options)
    }

    async append(callback) {
        return callback && (await callback())
    }

    async appendXmlFile(path, optional = false, reloadOnChange = false) {
        const module = await import('./providers/xml')
        const data = await module.read(path)
        if (data) {
            this.merge(data)
        } else {
            if (!optional) {
                throw new Error(path + ' is not found')
            }
        }
        return this
    }

    async appendJsonFile(path, optional = false, reloadOnChange = false) {
        const module = await import('./providers/json')
        const data = await module.read(path)
        if (data) {
            this.merge(data)
        } else {
            if (!optional) {
                throw new Error(path + ' is not found')
            }
        }
    }

    async appendYamlFile(path, optional = false, reloadOnChange = false) {
        const module = await import('./providers/yaml')
        const data = await module.read(path)
        if (data) {
            this.merge(data)
        } else {
            if (!optional) {
                throw new Error(path + ' is not found')
            }
        }
    }

    async appendDatabase({ url, table, key, value }) {
        const dbConfig = {
            settingFromDb: 'valueFromDb'
        }
        this.merge(dbConfig)
        return this
    }

    async appendRedis(url, key) {
        const module = await import('./providers/redis')
        const data = await module.read(url, key)
        if (data) {
            this.merge(data)
        } else {
            if (!optional) {
                throw new Error(key + ' is not found')
            }
        }
    }

    async appendUrl(url, options, optional) {
        const response = await fetch(url, options)
        const json = response.json
        if (json) {
            this.merge(xmlObject)
        } else {
            if (!optional) {
                throw new Error(path + 'is not found')
            }
        }
    }

    appendEnvironmentVariables(prefix, isRemovePrefix) {
        let env = {}
        for (let key in process.env) {
            if (key.startsWith(prefix)) {
                if (isRemovePrefix) {
                    key = key.slice(prefix.length)
                }
                env[key] = process.env[key]
            }
        }
        this.merge(env)
    }

    appendCommandLine() {
        const args = process.argv.slice(2)
        const commands = toCommandLine(args)
        this.merge(commands)
    }

    createVersionChange(data, options) {}

    merge(data, source) {
        const kvs = toPropertyList(data)
        kvs.forEach(it => {
            let key = it.paths.join('.')
            this.mergeItem(key, it.value, source)
        })
    }

    mergeItem(key, value, source) {
        let options = this.configurations.get(key)
        let item = {
            value: value,
            source: source
        }
        if (options) {
            options.values.push(item)
        } else {
            this.configurations.set(key, {
                values: [item],
                activeIndex: -1
            })
        }
    }

    update(data, options) {
        this.merge(data)
        if (options?.isCreateChanges || this.opts.isCreateChanges) {
            this.createVersionChange(data, options)
        }
    }

    // updateByKey(key) {
    //     this.
    // }

    toMap() {
        return this.configurations
    }

    get value() {
        const config = {}
        this.configurations.forEach((value, key) => {
            config[key] = value.values.at(value.activeIndex)?.value
        })
        return config
    }

    set value(data) {
        this.update(data)
    }

    [Symbol.toPrimitive](name) {
        const value = this.configurations.get(name)
        if (value) {
            return value.values.at(value.activeIndex)
        }
    }
}
